﻿/**
 * The CLIK ScrollIndicator displays the scroll position of another component, such as a multiline textField. It can be pointed at a textField to automatically display its scroll position. All list-based components as well as the TextArea have a scrollBar property which can be pointed to a ScrollIndicator or ScrollBar instance or linkage ID. 
 
	<b>Inspectable Properties</b>
	The inspectable properties of the ScrollIndicator are:<ul>
	<li><i>scrollTarget</i>: Set a TextArea or normal multiline textField as the scroll target to automatically respond to scroll events. Non-text field types have to manually update the ScrollIndicator properties.</li>
	<li><i>visible</i>: Hides the component if set to false.</li>
	<li><i>disabled</i>: Disables the component if set to true.</li>
	<li><i>offsetTop</i>: Thumb offset at the top. A positive value moves the thumb's top-most position higher.</li>
	<li><i>offsetBottom</i>: Thumb offset at the bottom. A positive value moves the thumb's bottom-most position lower.</li>
	<li><i>enableInitCallback</i>: If set to true, _global.CLIK_loadCallback() will be fired when a component is loaded and _global.CLIK_unloadCallback will be called when the component is unloaded. These methods receive the instance name, target path, and a reference the component as parameters.  _global.CLIK_loadCallback and _global.CLIK_unloadCallback should be overriden from the game engine using GFx FunctionObjects.</li>
	<li><i>soundMap</i>: Mapping between events and sound process. When an event is fired, the associated sound process will be fired via _global.gfxProcessSound, which should be overriden from the game engine using GFx FunctionObjects.</li></ul>
	
	<b>States</b> 
	The ScrollIndicator does not have explicit states. It uses the states of its child elements, the thumb and track Button components.
	
	<b>Events</b>
	All event callbacks receive a single Object parameter that contains relevant information about the event. The following properties are common to all events. <ul>
	<li><i>type</i>: The event type.</li>
	<li><i>target</i>: The target that generated the event.</li></ul>
		
	The events generated by the ScrollIndicator component are listed below. The properties listed next to the event are provided in addition to the common properties.<ul>
	<li><i>show</i>: The component’s visible property has been set to true at runtime.</li>
	<li><i>hide</i>: The component’s visible property has been set to false at runtime.</li>
	<li><i>scroll</i>: The scroll position has changed.<ul>
		<li><i>position</i>: The new scroll position. Number type. Values minimum position to maximum position. </li></ul></li></ul>
 */

/**************************************************************************

Filename    :   ScrollIndicator.as

Copyright   :   Copyright 2011 Autodesk, Inc. All Rights reserved.

Use of this software is subject to the terms of the Autodesk license
agreement provided at the time of installation or download, or which
otherwise accompanies this software in either electronic or hard copy form.

**************************************************************************/

/*
*/
import flash.external.ExternalInterface; 
import gfx.controls.Button;
import gfx.core.UIComponent;

[InspectableList("disabled", "visible", "inspectableScrollTarget", "offsetTop", "offsetBottom", "enableInitCallback", "soundMap")]
class gfx.controls.ScrollIndicator extends UIComponent {
	
// Constants:

// Public Properties:
	/** Whether the component is horizontal or vertical. This property is auto-set on stage components based on their rotation. */
	public var direction:String = "vertical";
	/** Mapping between events and sound process */
	[Inspectable(type="Object", defaultValue="theme:default,scroll:scroll")]
	public var soundMap:Object = { theme:"default", scroll:"scroll" };
	
// Private Properties:
	private var pageSize:Number;
	private var pageScrollSize:Number = 1;
	private var minPosition:Number = 0;
	private var maxPosition:Number = 10;
	private var _position:Number = 5;
	private var _scrollTarget:Object;
	[Inspectable(type="String", name="scrollTarget")]
	private var inspectableScrollTarget:Object;
	[Inspectable(defaultValue="0", verbose=1)]
	private var offsetTop:Number = 0;
	[Inspectable(defaultValue="0", verbose=1)]
	private var offsetBottom:Number = 0;
	private var lastVScrollPos:Number;
	private var scrollerIntervalID:Number;
	private var isDragging:Boolean = false;
	private var scrollTargetSelection:Array;
	
// UI Elements:
	/** A reference to the thumb symbol in the ScrollIndicator. */
	public var thumb:Button;
	/** A reference to the track symbol in the ScrollIndicator. */
	public var track:MovieClip;


// Initialization:
	/**
	 * The constructor is called when a ScrollIndicator or a sub-class of ScrollIndicator is instantiated on stage or by using {@code attachMovie()} in ActionScript. This component can <b>not</b> be instantiated using {@code new} syntax. When creating new components that extend ScrollIndicator, ensure that a {@code super()} call is made first in the constructor.
	 */
	public function ScrollIndicator() { 
		super(); 
		tabChildren = false;
		focusEnabled = tabEnabled = !_disabled;		
	}

// Public Methods:
	/**
	 * Disable this component. 
	 */
	[Inspectable(defaultValue="false")]
	public function get disabled():Boolean { return _disabled; }
	public function set disabled(value:Boolean):Void {
		if (_disabled == value) { return; }
		super.disabled = value;
		focusEnabled = tabEnabled = !_disabled;
		if (_scrollTarget) { tabEnabled = false; }
		if (initialized) { thumb.disabled = _disabled; }
	}

	/**
	 * Set the scroll properties of the component.
	 * @param pageSize The size of the pages to determine scroll distance.
	 * @param minPosition The minimum scroll position.
	 * @param maxPosition The maximum scroll position.
	 * @param pageScrollSize The amount to scroll when "paging". Not currently implemented.
	 */
	public function setScrollProperties(pageSize:Number, minPosition:Number, maxPosition:Number, pageScrollSize:Number):Void {
		this.pageSize = pageSize;
		if (pageScrollSize != undefined) this.pageScrollSize = pageScrollSize;
		this.minPosition = minPosition;
		this.maxPosition = maxPosition;
		updateThumb();
	}
	
	/** 
	 * Set the scroll position to a number between the minimum and maximum.
	 */
	public function get position():Number { return _position; }
	public function set position(value:Number):Void {
		if (value == _position) { return; }
		_position = Math.max(minPosition, Math.min(maxPosition, value));
		dispatchEventAndSound({type:"scroll", position:_position});
		invalidate();
	}
	
	/**
	 * Manually update the scrollBar when the target changes.
	 */
	public function update():Void {
		//onScoller();
	}
	
	/**
	 * Set a text target for the ScrollIndicator to respond to scroll changes.  
	 	Non-text fields have to manually update the properties.
	 */
	public function get scrollTarget():Object { return _scrollTarget; }
	public function set scrollTarget(value:Object):Void {
		//if (_scrollTarget == value) { return; }
		var _prevScrollTarget:Object = _scrollTarget;
		_scrollTarget = value;
		
		if (_prevScrollTarget && (value._parent != _prevScrollTarget)) {
			_prevScrollTarget.removeListener(this);
			if (_prevScrollTarget.scrollBar != null) { _prevScrollTarget.scrollBar = null; }	
			focusTarget = null;
			_prevScrollTarget.noAutoSelection = false;
		}		
		
		// Check if the scrollTarget is one a component, and if it has a scrollBar property (like a List)
		if (value instanceof UIComponent && value.scrollBar !== null) {
			value.scrollBar = this;
			return;
		}
		
		if (_scrollTarget == null) { 
			tabEnabled = true;
			return; 
		}
		_scrollTarget.addListener(this);
		_scrollTarget.noAutoSelection = true;
		focusTarget = _scrollTarget;
		tabEnabled = false;		
		onScroller();
	}
	
	/**
	 * Returns the available scrolling height of the component.
	 */
	public function get availableHeight():Number {
		return (direction == "horizontal" ? __width : __height) - thumb.height + offsetBottom + offsetTop;
	}
		
	/** @exclude */	
	public function toString():String {
		return "[Scaleform ScrollIndicator " + _name + "]";
	}
	
// Private Methods:
	private function configUI():Void {
		super.configUI();
		
		if (track == null) { track = new Button(); }		
		thumb.focusTarget = this; track.focusTarget = this;
		thumb.disabled = _disabled; // track is not a Button instance
		onRelease = function() {}
		useHandCursor = false;
		
		initSize();
		direction = (_rotation != 0) ? "horizontal" : "vertical";
		
		if (inspectableScrollTarget != null) {
			var target:Object = _parent[inspectableScrollTarget];
			if (target != null) { scrollTarget = target; }
			inspectableScrollTarget = null;
		}				
	}
	
	private function draw():Void {
		track._height = (direction=="horizontal") ? __width : __height;
		// Special case for textFields. Explicitly change the scroll properties as it may have changed.
		if (_scrollTarget instanceof TextField) {
			setScrollProperties(_scrollTarget.bottomScroll - _scrollTarget.scroll, 1, _scrollTarget.maxscroll);
		} else {		
			updateThumb();
		}
	}
	
	private function updateThumb():Void {
		// Draw Thumb Size
		if (!thumb.initialized) { invalidate(); return; } // This ensures we do not try and resize the thumb until it is ready.
		if (_disabled) { return; }
		
		var per:Number = Math.max(1, maxPosition - minPosition + pageSize);
		var trackHeight:Number = (direction == "horizontal" ? __width : __height) + offsetTop + offsetBottom;
		thumb.height = Math.max(10, pageSize / per * trackHeight); 
		
		// Thumb Position		
		var percent:Number = (position - minPosition) / (maxPosition - minPosition);	
		var top:Number = -offsetTop;
		var yPos:Number = (percent * availableHeight) + top;
		
		thumb._y = Math.max(top, Math.min(trackHeight - offsetTop, yPos));
		thumb.visible = !(isNaN(percent) || maxPosition == 0);
	}
		
	// The scrollTarget TextField has changed its scroll position. 
	private function onScroller():Void {
		if (isDragging) { return; } // Don't listen for scroll events while the thumb is dragging.
		if (lastVScrollPos == _scrollTarget.scroll) { 
			delete(lastVScrollPos); 
			return;
		}
		
        setScrollProperties(_scrollTarget.bottomScroll - _scrollTarget.scroll, 1, _scrollTarget.maxscroll);
		position = _scrollTarget.scroll;		
		
		 // MM BUG : When scrolling a TextField manually, the onScroller event doesn't always get
		 // fired when the field stops scrolling.  Need to use setInterval to manually call onScroller
		 // so we get the most updated scroll values.
		lastVScrollPos = _scrollTarget.scroll;
		if (scrollerIntervalID == undefined) {
			scrollerIntervalID = setInterval(this, "scrollerDelayUpdate", 10);
		}
	}
	
	private function scrollerDelayUpdate():Void {
		onScroller();
		clearInterval(scrollerIntervalID);
		delete(scrollerIntervalID);
	}
}